//
//  AgoraCamera.swift
//  AgoraPremium
//
//  Created by GongYuhua on 2017/11/10.
//  Copyright © 2017年 Agora. All rights reserved.
//

import Cocoa
// TODO: remove
class AgoraCamera: NSObject {
    var consumer: AgoraVideoFrameConsumer?
    
    var type: AgoraVideoBufferType = .pixelBuffer
    var format = AgoraVideoPixelFormat.NV12
    
    private var position = AVCaptureDevice.Position.front
    private var captureSession: AVCaptureSession?
    private var captureQueue: DispatchQueue?
    private var currentOutput: AVCaptureVideoDataOutput? {
        if let outputs = self.captureSession?.outputs as? [AVCaptureVideoDataOutput] {
            return outputs.first
        } else {
            return nil
        }
    }
    
    override init() {
        super.init()
    }
    
}

private extension AgoraCamera {
    func initialize() -> Bool {
        captureQueue = DispatchQueue(label: "AgoraPremiumVideoCaptureQueue")
        
        captureQueue?.async { [weak self] in
            guard let strongSelf = self else {
                return
            }
            
            let captureSession = AVCaptureSession()
            let captureOutput = AVCaptureVideoDataOutput()
            
            var formatType: OSType = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
            switch strongSelf.format {
                case .BGRA:         formatType = kCVPixelFormatType_32BGRA
                    break
                case .I420, .NV12:  formatType = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
            }
            
            captureOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: formatType]
            if captureSession.canAddOutput(captureOutput) {
                captureSession.addOutput(captureOutput)
            }
            
            strongSelf.captureSession = captureSession
        }
        
        return true
    }
    
    func startCapture() {
        guard let captureQueue = captureQueue else {
            return
        }
        
        captureQueue.async { [weak self] in
            guard let strongSelf = self, let captureSession = strongSelf.captureSession else {
                return
            }
            
            guard let currentOutput = strongSelf.currentOutput else {
                return
            }
            
            currentOutput.setSampleBufferDelegate(self, queue: captureQueue)
            
            strongSelf.changeCaptureDevice(toPosition: strongSelf.position, ofSession: captureSession)
            captureSession.beginConfiguration()
            if captureSession.canSetSessionPreset(.high) {
                captureSession.sessionPreset = .high
            }
            captureSession.commitConfiguration()
            captureSession.startRunning()
        }
    }
    
    func stopCapture() {
        captureQueue?.async { [weak self] in
            self?.currentOutput?.setSampleBufferDelegate(nil, queue: nil)
            self?.captureSession?.stopRunning()
        }
    }
    
    func dispose() {
        captureQueue?.async { [weak self] in
            self?.captureSession = nil
        }
        captureQueue = nil
    }
}

private extension AgoraCamera {
    func changeCaptureDevice(toPosition position: AVCaptureDevice.Position, ofSession captureSession: AVCaptureSession) {
        guard let captureDevice = captureDevice(atPosition: position) else {
            return
        }
        
        let currentInputs = captureSession.inputs as? [AVCaptureDeviceInput]
        let currentInput = currentInputs?.first
        
        if let currentInput = currentInput, currentInput.device.localizedName == captureDevice.uniqueID {
            return
        }
        
        guard let newInput = try? AVCaptureDeviceInput(device: captureDevice) else {
            return
        }
        
        if let currentInput = currentInput {
            captureSession.removeInput(currentInput)
        }
        if captureSession.canAddInput(newInput) {
            captureSession.addInput(newInput)
        }
    }
    
    func captureDevice(atPosition position: AVCaptureDevice.Position) -> AVCaptureDevice? {
        //let devices = AVCaptureDevice.devices(for: AVMediaType.video)
        let device = AVCaptureDevice.DiscoverySession(deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera], mediaType: AVMediaType.video, position: AVCaptureDevice.Position.front)
        return device.devices.first
    }
}

extension AgoraCamera: AgoraVideoSourceProtocol {
    func shouldInitialize() -> Bool {
        return initialize()
    }
    
    func shouldStart() {
        startCapture()
    }
    
    func shouldStop() {
        stopCapture()
    }
    
    func shouldDispose() {
        dispose()
    }
    
    func bufferType() -> AgoraVideoBufferType {
        return type
    }
    
    func captureType() -> AgoraVideoCaptureType {
        return AgoraVideoCaptureType.camera
    }
    
    func contentHint() -> AgoraVideoContentHint {
        return AgoraVideoContentHint.none
    }
    
}

extension AgoraCamera: AVCaptureVideoDataOutputSampleBufferDelegate {
    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer), CVPixelBufferLockBaseAddress(imageBuffer, .readOnly) == kCVReturnSuccess else {
            return
        }
        defer {
            CVPixelBufferUnlockBaseAddress(imageBuffer, .readOnly)
        }
        
        let time = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)

        let rotation: AgoraVideoRotation = .rotationNone
        
        switch type {
        case .pixelBuffer:
            let buffer: CVPixelBuffer
            if format == .I420 {
                buffer = AgoraVideoFormatConverter.convertPixelBufferFromNV12ToI420(imageBuffer)
                
            } else {
                buffer = imageBuffer
            }
            //consumer?.consumePixelBuffer(buffer, withTimestamp: time, rotation: rotation)
              consumer?.consumePixelBuffer(buffer, withTimestamp: time, rotation: AgoraVideoRotation(rawValue: 0)!)
            break;
        case .rawData:
            let rawDate = AgoraVideoFormatConverter.convertPixelBuffer(imageBuffer, toRawDataOf: format)
            consumer?.consumeRawData(rawDate.pointer, withTimestamp: time, format: format, size: rawDate.size, rotation: rotation)
            if (format != .BGRA) {
                free(rawDate.pointer)
            }
        default:
            break;
        }
    }
}
